#include "CHttpReq.h"
#include <wx/msgdlg.h>
#include <wx/base64.h>
#include <wx/sstream.h>
#include "Utils.h"
#include "OAuth.h"

CHttpReq::CHttpReq (const bool auth_IN , const int retrySec_IN, const int timeOutSec_IN)
:retrySec(retrySec_IN)
,timeOutSec(timeOutSec_IN)
,auth(auth_IN)
,mpHttpReqCS(NULL)
,reqResult("")
,retryCount(0)
,httpErrCode(-1)
,errorInfo("")
{
    httpReq.SetTimeout(timeOutSec); // 10 seconds of timeout instead of 10 minutes ...
    mpHttpReqCS = new wxCriticalSection();
}

CHttpReq::~CHttpReq()
{
    if(NULL != mpHttpReqCS)
    {
        delete mpHttpReqCS;
        mpHttpReqCS=NULL;
    }

    httpReq.Close();
}

//TO DO : a recursive version
//TO DO : a RegEx version
void CHttpReq::extractBaseURLAndQueryParsms(const wxString& fullURL)
{
    mQueryParamsTable.clear();
    mOauthBaseURL.Clear();

    if(wxNOT_FOUND!=fullURL.Find("?"))    //There'll be only one '?' in the URL
    {
        mOauthBaseURL = fullURL.BeforeFirst('?');
        wxString rawParamStr = fullURL.AfterFirst('?');
        wxString paramsUnit("");
        while(!rawParamStr.IsEmpty())
        {
            int seperatorIndex = rawParamStr.Find('&');
            if(seperatorIndex!=wxNOT_FOUND)
            {
                //extract query params into map type
                paramsUnit=rawParamStr.Left(seperatorIndex);
                mQueryParamsTable[paramsUnit.BeforeFirst('=')] = paramsUnit.AfterFirst('=');
                rawParamStr = rawParamStr.Right(rawParamStr.Length()-seperatorIndex-1);
            }
            else
            {
                mQueryParamsTable[rawParamStr.BeforeFirst('=')] = rawParamStr.AfterFirst('=');
                break;
            }
        }
    }
    else
    {
        //no query param
        mOauthBaseURL = fullURL;
    }
}

void CHttpReq::generateOauthAccessHeader(const wxString& fullURL , const wxString& _httpType)
{
    extractBaseURLAndQueryParsms(fullURL);

    Oauth& _oauth = Oauth::GetInstance();
    _oauth.resetData(); //keep clean
    _oauth.setExtraQueryParams(mQueryParamsTable);
    _oauth.signRequest(_oauth.getBaseString(mOauthBaseURL));    // Base string require a base URL only , not a full format
    setOauthHeader( _oauth.getOauthHeader(), _httpType);
    #ifdef HTTP_DEBUG_LOG
    LOGMAN.logInfo(wxString("generateOauthAccessHeader for ")<<fullURL);
    LOGMAN.logInfo(wxString("auto generated base string :")<<_oauth.getBaseString(fullURL));
    LOGMAN.logInfo(wxString("auto generated OauthHeader :")<<_oauth.getOauthHeader());
    #endif
}

bool CHttpReq::retryConn(const wxString& destHost)
{
    retryCount = 0;
    int retryTotal = timeOutSec/retrySec;

     //this may be why sometimes you get a response code 0 : the previous connection is still working before timeout
    //if(httpReq.IsConnected())  httpReq.Close();

    //Main thread will return null as This(), 'cause main thread is not built from wxThread
    if(wxThread::IsMain() || wxThread::This()==NULL)
    {
        while (!httpReq.Connect(destHost) && retryCount<=retryTotal)    //holy shit, take care of these loop conditions
        {
            wxSleep(retrySec);  //in second
            ++retryCount;
        }
    }
    else
    {
        //When this is a secondary thread
        while (wxThread::This()!=NULL && !wxThread::This()->TestDestroy())
        {
            if(httpReq.Connect(destHost) || retryCount>retryTotal) break;
            wxThread::Sleep((retrySec+retryCount)*1000);    //in milliseconds
            ++retryCount;
        }
    }

    if(retryCount<retryTotal) return true;
    return false;
}

bool CHttpReq::startReq(const wxString& urlStr_IN,const wxString& postBuf)
{
    wxCriticalSectionLocker httpReqCSLocker(*mpHttpReqCS);

    //IMPORTANT: string req result uses stream output , which means append to the last by default !
    reqResult.Clear();
    errorInfo.Clear();
    //The set header only affect request once
    httpReq.SetHeader(wxT("Content-type"), wxT("text/html; charset=utf-8"));

    //set oauth header
    if(auth)
    {
        if(mOauthData.mHttpHeader.IsEmpty())    //Get header on my own only when not set
        {
            if(postBuf.IsEmpty())
            {
                generateOauthAccessHeader(urlStr_IN, HTTP_METHOD_GET);
            }
            else
            {
                generateOauthAccessHeader(urlStr_IN, HTTP_METHOD_POST);
            }
        }
        assert(!mOauthData.mHttpHeader.IsEmpty());
        httpReq.SetHeader(wxT("Authorization"), mOauthData.mHttpHeader);
    }

    /***********************************************************
    //  If no POST data , http req will use GET as default method
    //  This must correspond to the http method hard coded in base string
    //  Otherwise , this will raise an invalid signature under OAuth
    ************************************************************/
    //check oauth settings
    if(!postBuf.IsEmpty())
    {
        httpReq.SetPostBuffer(postBuf);
        assert(!mOauthData.mHttpHeader.IsEmpty() ? mOauthData.mHttpMethod==HTTP_METHOD_POST : true);
    }
    else
    {
        assert(!mOauthData.mHttpHeader.IsEmpty() ? mOauthData.mHttpMethod==HTTP_METHOD_GET : true);
    }

    splitURL(urlStr_IN);    // split URL to host name and dest path
    if(!retryConn(destHost))    // try to connect host
    {
        #ifdef HTTP_WTF
        errorInfo = destHost+wxString(" <Header>")<<mOauthData.mHttpHeader;
        #endif
        mOauthData.mHttpHeader.Clear();
        return false;
    }
    httpStream = httpReq.GetInputStream(destPath);

#ifdef HTTP_DEBUG
    switch(httpReq.GetError()){
        case wxURL_NOERR:
            wxMessageBox("NoError");
            break;
        case wxURL_SNTXERR:
            wxMessageBox("Syntax error in the URL string");
            break;
        case wxURL_NOPROTO:
            wxMessageBox("Found no protocol which can get this URL.");
            break;
        case wxURL_NOHOST:
            wxMessageBox("A host name is required for this protocol.");
            break;
        case wxURL_NOPATH:
            wxMessageBox("A path is required for this protocol.");
            break;
        case wxURL_CONNERR:
            wxMessageBox("Connection error.");
            break;
        case wxURL_PROTOERR:
            wxMessageBox("An error occurred during negotiation.");
            break;
        default:
            wxMessageBox("Unknown Error");
    }
#endif


    httpErrCode = httpReq.GetResponse();
    //NOTE : Within poor network condition ,
    //httpStream can be assigned a null value from httpReq.GetInputStream(destPath)
    //httpStream should be deleted before req closed
    if ((httpErrCode == OK || httpErrCode == 0) && httpStream != NULL)
    {
        wxStringOutputStream out_stream(&reqResult);
        httpStream->Read(out_stream);   // stream in HTTP incoming content
        wxDELETE(httpStream);
        mOauthData.mHttpHeader.Clear();
        return true;
    }
    else
    {
        #ifdef HTTP_WTF
            //Orz sometimes the server gives you 0 as a response code...
            errorInfo<<wxString("Unexpected error code : ")<<httpErrCode;
        #endif
        mOauthData.mHttpHeader.Clear();
        return false;
    }

}//end of bool CHttpReq::startReq()
